package com.test.hello.common;

import java.util.List;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.test.hello.model.Role;
import com.test.hello.model.User;
import com.test.hello.service.UserService;
@Service
@Transactional
public class MyShiro extends AuthorizingRealm  {
	private static final Logger log = Logger.getLogger(AuthorizingRealm.class);
	
	@Autowired
	private UserService userService;
	
	/**
     * 为当前登录的Subject授予角色和权限
     * 经测试:本例中该方法的调用时机为需授权资源被访问时
     * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		 log.info("-----------------access doGetAuthorizationInfo----------------------");
		 
		/* //获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()
	        String currentUsername = (String)super.getAvailablePrincipal(principals);
//	      这里实现自己的业务逻辑
//	      根据业务逻辑，设置角色和权限

	        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
	        //实际中可能会像上面注释的那样从数据库取得
	        if(null!=currentUsername && "mike".equals(currentUsername)){
	            //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
	            simpleAuthorInfo.addRole("admin");         
	            simpleAuthorInfo.addStringPermission("admin:manage"); //添加权限return simpleAuthorInfo;
	        }
	        //若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址
	        //详见applicationContext.xml中的<bean id="shiroFilter">的配置*/
		 
		//获取登录时输入的用户名  
	        String loginName=(String) principals.fromRealm(getName()).iterator().next();  
	        //到数据库查是否有此对象  
	        User user=userService.findByName(loginName);  
	        if(user!=null){  
	            //权限信息对象info,用来存放查出的用户的所有的角色（role）及权限（permission）  
	            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();  
	            //用户的角色集合  
	            info.setRoles(user.getRolesName());  
	            //用户的角色对应的所有权限，如果只使用角色定义访问权限，下面的四行可以不要  
	            List<Role> roleList=user.getRoleList();  
	            for (Role role : roleList) {
	            	System.out.println(role.getPermissionsName());
	                info.addStringPermissions(role.getPermissionsName());  
	            } 
	            return info;  
	        }  
		 
		 
		 
		 
		return null;
	}

	
	 /**
     *   验证当前登录的Subject
     *   经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {//获取基于用户名和密码的令牌
      /*  //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
        //两个token的引用都是一样的
        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
        log.info("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
        //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)
        //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证
        if("mike".equals(token.getUsername())){
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("mike", "mike", this.getName());
            authcInfo.getCredentials();
            this.setSession("currentUser", "mike");
            return authcInfo;
        }
        ///没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
        return null;*/
    	
    	
    	
    	//UsernamePasswordToken对象用来存放提交的登录信息  
        UsernamePasswordToken token=(UsernamePasswordToken) authcToken;  
        //查出是否有此用户  
        User user=userService.findByName(token.getUsername());  
        if(user!=null){  
            //若存在，将此用户存放到登录认证info中  
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());  
        }  
        return null;  
    	
    }

    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     *   比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setSession(Object key, Object value){
        Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();if(null != session){
                session.setAttribute(key, value);
            }
        }
    }

}
